package org.zturn.exhibition.encrypt;
 
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.ObjectUtils;
import org.zturn.exhibition.util.AESUtil;
import org.zturn.exhibition.util.RSAUtil;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
 
 
/**
 * @ClassName : DecryptReplaceStreamFilter
 * @Description :Filter过滤器 解密请求参数。同时替换请求体，使后续操作无感知加密！！
 * @Author : AD
 */
@Slf4j
public class DecryptReplaceStreamFilter implements Filter{
 
    /*
    * AKS（Authentication Key Management System），采用无明文密钥的方式对数据进行加密保护，加解密运算由统一的安全计算中心完成，AKS系统以接口的方式为对各个业务线提供加密解服务。
    * 各个系统不再使用明文密钥，只使用密钥别名，调用简单的加解密函数，完成对数据的保护。
    * */
    //请求头标签开关：是否需要加密解密
    private static final String AKS_ENABLE = "aksEncrypt";
    //GET请求加密数据Key
    public static final String AKS_PARAMETER = "encryptData";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_POST ="POST";
 
    //POST请求加密数据Key
    private static final String AKS_BODY ="content";
    //AES密钥Key
    private static final String AES_KEY = "aesKey";
 
    /**
     * Feign是声明式Web Service客户端，它让微服务之间的调用变得更简单，类似controller调用service。
     * Feign内部调用时，请求头中标注请求源
     * */
    public static final String SOURCE_KEY = "api-source";
    public static final String SOURCE_VALUE = "inner-api";
 
    @Value("${Rsa.PrivateKey}")
    private String privateKey;
 
    @Value("${API.Security.enable}")
    private boolean securityEnable;
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {
        //转换为自己Wrapper,实现多次读写
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        String contentType = requestWrapper.getContentType();  //请求头中获取Content-Type数据
        String requestURI = requestWrapper.getRequestURI();
        //配置文件配置是否开启加解密功能
        if (!securityEnable)
        {
            log.info("未开启接口安全加密传输! 无需解密请求参数！");
            filterChain.doFilter(requestWrapper,servletResponse);
            return;
        }
        //通过请求头参数判断该请求是否需要解密处理
        if (!needAks(requestWrapper)){
            log.info("请求：{}，非加密请求，无需解密操作！",requestURI);
            filterChain.doFilter(requestWrapper,servletResponse);
            return;
        }
        /*
        * [该功能暂时不用管，因为在请求头中不添加 aksEncrypt：true  键值对，请求接口时同样不会去进行解密请求数据的操作]
        * */
        //Feign服务端调用内部请求，按照不加密的逻辑放行[Feign是声明式Web Service客户端，它让微服务之间的调用变得更简单，类似controller调用service。]
        //前端 --> A --> b --> c
        String sourceKey = requestWrapper.getHeader(SOURCE_KEY);
        if (!ObjectUtils.isEmpty(sourceKey) && sourceKey.equals(SOURCE_VALUE) ){
            log.info("内部请求，无效加密解密接口数据！");
            filterChain.doFilter(requestWrapper,servletResponse);
            return;
        }
 
        /*
        * POST请求进行解密工作
        * */
        if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_POST)){
            //读取JSON请求体数据
            StringBuffer bodyInfo = new StringBuffer();
            String line = null;
            BufferedReader reader = null;
            reader = requestWrapper.getReader();
            while ( ( line = reader.readLine() ) != null ){
                bodyInfo.append(line);
            }
            //解密请求体数据
            JSONObject jsonObject = JSONObject.parseObject(bodyInfo.toString());
            log.info("Post请求:{}，待解密请求参数：{}", requestURI, JSONObject.toJSONString(jsonObject));
            //获取通过AES加密之后的密文
            String content = jsonObject.getString(AKS_BODY);
            //获取通过RSA加密之后的AES密钥KEy
            String aesKey = jsonObject.getString(AES_KEY);
            //RSAUtil解密出AES密钥Key
            try {
                aesKey = new String(RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey),privateKey,RSAUtil.MAX_DECRYPT_BLOCK),"UTF-8");
            } catch (Exception e) {
                throw new  RuntimeException(e);
            }
            /*
            * 方式1.将解密之后的数据+aesKey [放入body中，弃：会影响body结构] ( 满足在AOP操作中对出参数据进行加密 ) 一并交给下游业务。
            */
            //JSONObject requestBody = JSONObject.parseObject(data);
            //requestBody.put(AES_KEY,aesKey);
            /*
             * 方式2.将其放入[请求对象属性中中]，不影响请求体结构！
            */
            //requestWrapper.setAttribute(AES_KEY,aesKey);
            /*
            * 方式3.将其放入[放入RequestWrapper封装的额外参数Map中]，不影响请求体结构！
            * */
            HashMap<String, Object> map = new HashMap<>();
            map.put(AES_KEY,aesKey);
            requestWrapper.setParameterMap(map);
 
            //AESUtil + aesKey 解密json数据
            String decryptData = AESUtil.decryptFromBase64(content, aesKey);
            log.info("Get请求:{}，解密之后参数：{}", requestURI, decryptData);
 
            //重置Json请求体，保证下游业务无感知获取数据
            //requestWrapper.setBody(requestBody.toJSONString().getBytes());
            requestWrapper.setBody(decryptData.getBytes());
        }
        /*
         * GET请求解密处理：[AES密钥Key放置在请求头中]
         * */
        else if (requestWrapper.getMethod().equalsIgnoreCase(METHOD_GET))
        {
            //Get请求中 获取到指定加密的参数 然后进行解密操作
            String encryptData = requestWrapper.getParameter(AKS_PARAMETER);
            log.info("Get请求:{}，待解密请求参数：{}", requestWrapper.getRequestURI(), encryptData);
            // 先解密 存放在请求头中且经过RSA加密过的AES密钥
            String aesKey = requestWrapper.getHeader(AES_KEY);
            if (encryptData != null && !encryptData.isEmpty() && aesKey != null && !aesKey.isEmpty()){
                try {
                    byte[] aesKeyByte = RSAUtil.decryptByPrivateKey(RSAUtil.toBytes(aesKey), privateKey, RSAUtil.MAX_DECRYPT_BLOCK);
                    aesKey = new String(aesKeyByte,"UTF-8");
                    System.out.println("aesKey.toString() = " + aesKey.toString());
                } catch (Exception e) {
                    throw new  RuntimeException(e);
                }
                //Get请求中的加密数据是以16进制字符串方式传输
                byte[] encryptDataByte = AESUtil.toBytes(encryptData);
                System.out.println("Arrays.toString(encryptDataByte) = " + Arrays.toString(encryptDataByte));
                // 解密数据操作 AKS_PARAMETER
                String decryptData = new String(AESUtil.decrypt(encryptDataByte,aesKey.getBytes("UTF-8")),"UTF-8");
                log.info("Get请求:{}，解密之后参数：{}", requestURI, decryptData);
                //将GET请求中的Parameter参数赋值入RequestWrapper中封装的其它参数存放集合Map中
                com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
                Map<String, Object> map = objectMapper.readValue(decryptData, Map.class);
                //将aseKey数据也存入map额外参数中
                map.put("aesKey",aesKey);
                requestWrapper.setParameterMap(map);
            }
        }
        filterChain.doFilter(requestWrapper,servletResponse);
    }
 
    @Override
    public void init(FilterConfig filterConfig)throws ServletException {
        Filter.super.init(filterConfig);
    }
 
    @Override
    public void destroy(){
        Filter.super.destroy();
    }
 
    /**
     * Description: 判断当前请求是否需要加密解密数据
     *
     * @param request
     * @return boolean
    */
    private boolean needAks(HttpServletRequest request){
        String enableAKS = request.getHeader(AKS_ENABLE);
        return (enableAKS != null && enableAKS.equalsIgnoreCase("true"))? true: false;
    }
}